home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / ZipOutputStream.java < prev    next >
Text File  |  1998-09-22  |  14KB  |  433 lines

  1. /*
  2.  * @(#)ZipOutputStream.java    1.13 97/01/27
  3.  * 
  4.  * Copyright (c) 1995, 1996 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  * CopyrightVersion 1.1_beta
  20.  * 
  21.  */
  22.  
  23. package java.util.zip;
  24.  
  25. import java.io.OutputStream;
  26. import java.io.IOException;
  27. import java.util.Vector;
  28. import java.util.Hashtable;
  29. import java.util.Enumeration;
  30.  
  31. /**
  32.  * This class implements an output stream filter for writing files in the
  33.  * ZIP file format. Includes support for both compressed and uncompressed
  34.  * entries.
  35.  *
  36.  * @author    David Connelly
  37.  * @version    1.13, 01/27/97
  38.  */
  39. public
  40. class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
  41.     private ZipEntry entry;
  42.     private Vector entries = new Vector();
  43.     private Hashtable names = new Hashtable();
  44.     private CRC32 crc = new CRC32();
  45.     private long written;
  46.     private long locoff = 0;
  47.     private String comment;
  48.     private int method = DEFLATED;
  49.     private boolean finished;
  50.  
  51.     /**
  52.      * Compression method for uncompressed (STORED) entries.
  53.      */
  54.     public static final int STORED = ZipEntry.STORED;
  55.  
  56.     /**
  57.      * Compression method for compressed (DEFLATED) entries.
  58.      */
  59.     public static final int DEFLATED = ZipEntry.DEFLATED;
  60.  
  61.     /**
  62.      * Creates a new ZIP output stream.
  63.      * @param out the actual output stream 
  64.      */
  65.     public ZipOutputStream(OutputStream out) {
  66.     super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
  67.     }
  68.  
  69.     /**
  70.      * Sets the ZIP file comment.
  71.      * @param comment the comment string
  72.      * @exception IllegalArgumentException if the length of the specified
  73.      *          ZIP file comment is greater than 0xFFFF bytes
  74.      */
  75.     public void setComment(String comment) {
  76.     if (comment.length() > 0xffff) {
  77.         throw new IllegalArgumentException("invalid ZIP file comment");
  78.     }
  79.     this.comment = comment;
  80.     }
  81.  
  82.     /**
  83.      * Sets the default compression method for subsequent entries. This
  84.      * default will be used whenever the compression method is not specified
  85.      * for an individual ZIP file entry, and is initially set to DEFLATED.
  86.      * @param method the default compression method
  87.      * @exception IllegalArgumentException if the specified compression method
  88.      *          is invalid
  89.      */
  90.     public void setMethod(int method) {
  91.     if (method != DEFLATED && method != STORED) {
  92.         throw new IllegalArgumentException("invalid compression method");
  93.     }
  94.     this.method = method;
  95.     }
  96.  
  97.     /**
  98.      * Sets the compression level for subsequent entries which are DEFLATED.
  99.      * The default setting is DEFAULT_COMPRESSION.
  100.      * @param level the compression level (0-9)
  101.      * @exception IllegalArgumentException if the compression level is invalid
  102.      */
  103.     public void setLevel(int level) {
  104.     def.setLevel(level);
  105.     }
  106.  
  107.     /**
  108.      * Begins writing a new ZIP file entry and positions the stream to the
  109.      * start of the entry data. Closes the current entry if still active.
  110.      * The default compression method will be used if no compression method
  111.      * was specified for the entry, and the current time will be used if
  112.      * the entry has no set modification time.
  113.      * @param e the ZIP entry to be written
  114.      * @exception ZipException if a ZIP format error has occurred
  115.      * @exception IOException if an I/O error has occurred
  116.      */
  117.     public void putNextEntry(ZipEntry e) throws IOException {
  118.     if (entry != null) {
  119.         closeEntry();    // close previous entry
  120.     }
  121.     if (e.time == -1) {
  122.         e.setTime(System.currentTimeMillis());
  123.     }
  124.     if (e.method == -1) {
  125.         e.method = method;    // use default method
  126.     }
  127.     switch (e.method) {
  128.     case DEFLATED:
  129.         if (e.size == -1 || e.csize == -1 || e.crc == -1) {
  130.         // store size, compressed size, and crc-32 in data descriptor
  131.         // immediately following the compressed entry data
  132.         e.flag = 8;
  133.         } else if (e.size != -1 && e.csize != -1 && e.crc != -1) {
  134.         // store size, compressed size, and crc-32 in LOC header
  135.         e.flag = 0;
  136.         } else {
  137.         throw new ZipException(
  138.             "DEFLATED entry missing size, compressed size, or crc-32");
  139.         }
  140.         e.version = 20;
  141.         break;
  142.     case STORED:
  143.         // compressed size, uncompressed size, and crc-32 must all be
  144.         // set for entries using STORED compression method
  145.         if (e.size == -1) {
  146.         e.size = e.csize;
  147.         } else if (e.csize == -1) {
  148.         e.csize = e.size;
  149.         } else if (e.size != e.csize) {
  150.         throw new ZipException(
  151.             "STORED entry where compressed != uncompressed size");
  152.         }
  153.         if (e.size == -1 || e.crc == -1) {
  154.         throw new ZipException(
  155.             "STORED entry missing size, compressed size, or crc-32");
  156.         }
  157.         e.version = 10;
  158.         e.flag = 0;
  159.         break;
  160.     default:
  161.         throw new ZipException("unsupported compression method");
  162.     }
  163.     e.offset = written;
  164.     writeLOC(e);
  165.     if (names.put(e.name, e) != null) {
  166.         throw new ZipException("duplicate entry: " + e.name);
  167.     }
  168.     entries.addElement(e);
  169.     entry = e;
  170.     }
  171.  
  172.     /**
  173.      * Closes the current ZIP entry and positions the stream for writing
  174.      * the next entry.
  175.      * @exception ZipException if a ZIP format error has occurred
  176.      * @exception IOException if an I/O error has occurred
  177.      */
  178.     public void closeEntry() throws IOException {
  179.     ZipEntry e = entry;
  180.     if (e != null) {
  181.         switch (e.method) {
  182.         case DEFLATED:
  183.         def.finish();
  184.         while (!def.finished()) {
  185.             deflate();
  186.         }
  187.         if ((e.flag & 8) == 0) {
  188.             // verify size, compressed size, and crc-32 settings
  189.             if (e.size != def.getTotalIn()) {
  190.             throw new ZipException(
  191.                 "invalid entry size (expected " + e.size +
  192.                 " but got " + def.getTotalIn() + " bytes)");
  193.             }
  194.             if (e.csize != def.getTotalOut()) {
  195.             throw new ZipException(
  196.                 "invalid entry compressed size (expected " +
  197.                 e.csize + " but got " + def.getTotalOut() +
  198.                 " bytes)");
  199.             }
  200.             if (e.crc != crc.getValue()) {
  201.             throw new ZipException(
  202.                 "invalid entry CRC-32 (expected 0x" +
  203.                 Long.toHexString(e.crc) + " but got 0x" +
  204.                 Long.toHexString(crc.getValue()) + ")");
  205.             }
  206.         } else {
  207.             e.size = def.getTotalIn();
  208.             e.csize = def.getTotalOut();
  209.             e.crc = crc.getValue();
  210.             writeEXT(e);
  211.         }
  212.         def.reset();
  213.         written += e.csize;
  214.         break;
  215.         case STORED:
  216.         // we already know that both e.size and e.csize are the same
  217.         if (e.size != written - locoff) {
  218.             throw new ZipException(
  219.             "invalid entry size (expected " + e.size +
  220.             " but got " + (written - locoff) + " bytes)");
  221.         }
  222.         if (e.crc != crc.getValue()) {
  223.             throw new ZipException(
  224.              "invalid entry crc-32 (expected 0x" +
  225.              Long.toHexString(e.size) + " but got 0x" +
  226.              Long.toHexString(crc.getValue()) + ")");
  227.         }
  228.         break;
  229.         default:
  230.         throw new InternalError("invalid compression method");
  231.         }
  232.         crc.reset();
  233.         entry = null;
  234.     }
  235.     }
  236.  
  237.     /**
  238.      * Writes an array of bytes to the current ZIP entry data. This method
  239.      * will block until all the bytes are written.
  240.      * @param b the data to be written
  241.      * @param off the start offset in the data
  242.      * @param len the number of bytes that are written
  243.      * @exception ZipException if a ZIP file error has occurred
  244.      * @exception IOException if an I/O error has occurred
  245.      */
  246.     public synchronized void write(byte[] b, int off, int len)
  247.     throws IOException
  248.     {
  249.     if (entry == null) {
  250.         throw new ZipException("no current ZIP entry");
  251.     }
  252.     switch (entry.method) {
  253.     case DEFLATED:
  254.         super.write(b, off, len);
  255.         break;
  256.     case STORED:
  257.         written += len;
  258.         if (written - locoff > entry.size) {
  259.         throw new ZipException(
  260.             "attempt to write past end of STORED entry");
  261.         }
  262.         out.write(b, off, len);
  263.         break;
  264.     default:
  265.         throw new InternalError("invalid compression method");
  266.     }
  267.     crc.update(b, off, len);
  268.     }
  269.  
  270.     /**
  271.      * Finishes writing the contents of the ZIP output stream without closing
  272.      * the underlying stream. Use this method when applying multiple filters
  273.      * in succession to the same output stream.
  274.      * @exception ZipException if a ZIP file error has occurred
  275.      * @exception IOException if an I/O exception has occurred
  276.      */
  277.     public void finish() throws IOException {
  278.     if (finished) {
  279.         return;
  280.     }
  281.     if (entry != null) {
  282.         closeEntry();
  283.     }
  284.     if (entries.size() < 1) {
  285.         throw new ZipException("ZIP file must have at least one entry");
  286.     }
  287.     // write central directory
  288.     long off = written;
  289.     Enumeration e = entries.elements();
  290.     while (e.hasMoreElements()) {
  291.         writeCEN((ZipEntry)e.nextElement());
  292.     }
  293.     writeEND(off, written - off);
  294.     finished = true;
  295.     }
  296.  
  297.     /**
  298.      * Closes the ZIP output stream as well as the stream being filtered.
  299.      * @exception ZipException if a ZIP file error has occurred
  300.      * @exception IOException if an I/O error has occurred
  301.      */
  302.     public void close() throws IOException {
  303.     finish();
  304.     out.close();
  305.     }
  306.  
  307.     /*
  308.      * Writes local file (LOC) header for specified entry.
  309.      */
  310.     private void writeLOC(ZipEntry e) throws IOException {
  311.     writeInt(LOCSIG);        // LOC header signature
  312.     writeShort(e.version);      // version needed to extract
  313.     writeShort(e.flag);         // general purpose bit flag
  314.     writeShort(e.method);       // compression method
  315.     writeInt(e.time);           // last modification time
  316.     if ((e.flag & 8) == 8) {
  317.         // store size, uncompressed size, and crc-32 in data descriptor
  318.         // immediately following compressed entry data
  319.         writeInt(0);
  320.         writeInt(0);
  321.         writeInt(0);
  322.     } else {
  323.         writeInt(e.crc);        // crc-32
  324.         writeInt(e.csize);      // compressed size
  325.         writeInt(e.size);       // uncompressed size
  326.     }
  327.     writeShort(e.name.length());
  328.     writeShort(e.extra != null ? e.extra.length : 0);
  329.     writeAscii(e.name);
  330.     if (e.extra != null) {
  331.         writeBytes(e.extra, 0, e.extra.length);
  332.     }
  333.     locoff = written;
  334.     }
  335.  
  336.     /*
  337.      * Writes extra data descriptor (EXT) for specified entry.
  338.      */
  339.     private void writeEXT(ZipEntry e) throws IOException {
  340.     writeInt(EXTSIG);        // EXT header signature
  341.     writeInt(e.crc);        // crc-32
  342.     writeInt(e.csize);        // compressed size
  343.     writeInt(e.size);        // uncompressed size
  344.     }
  345.  
  346.     /*
  347.      * Write central directory (CEN) header for specified entry.
  348.      * REMIND: add support for file attributes
  349.      */
  350.     private void writeCEN(ZipEntry e) throws IOException {
  351.     writeInt(CENSIG);        // CEN header signature
  352.     writeShort(e.version);        // version made by
  353.     writeShort(e.version);        // version needed to extract
  354.     writeShort(e.flag);        // general purpose bit flag
  355.     writeShort(e.method);        // compression method
  356.     writeInt(e.time);        // last modification time
  357.     writeInt(e.crc);        // crc-32
  358.     writeInt(e.csize);        // compressed size
  359.     writeInt(e.size);        // uncompressed size
  360.     writeShort(e.name.length());
  361.     writeShort(e.extra != null ? e.extra.length : 0);
  362.     writeShort(e.comment != null ? e.comment.length() : 0);
  363.     writeShort(0);            // starting disk number
  364.     writeShort(0);            // internal file attributes (unused)
  365.     writeInt(0);            // external file attributes (unused)
  366.     writeInt(e.offset);        // relative offset of local header
  367.     writeAscii(e.name);
  368.     if (e.extra != null) {
  369.         writeBytes(e.extra, 0, e.extra.length);
  370.     }
  371.     if (e.comment != null) {
  372.         writeAscii(e.comment);
  373.     }
  374.     }
  375.  
  376.     /*
  377.      * Writes end of central directory (END) header.
  378.      */
  379.     private void writeEND(long off, long len) throws IOException {
  380.     writeInt(ENDSIG);        // END record signature
  381.     writeShort(0);            // number of this disk
  382.     writeShort(0);            // central directory start disk
  383.     writeShort(entries.size()); // number of directory entries on disk
  384.     writeShort(entries.size()); // total number of directory entries
  385.     writeInt(len);            // length of central directory
  386.     writeInt(off);            // offset of central directory
  387.     writeShort(comment != null ? comment.length() : 0);
  388.     if (comment != null) {
  389.         writeAscii(comment);    // ZIP file comment
  390.     }
  391.     }
  392.  
  393.     /*
  394.      * Writes a 16-bit short to the output stream in little-endian byte order.
  395.      */
  396.     private void writeShort(int v) throws IOException {
  397.     OutputStream out = this.out;
  398.     out.write((v >>> 0) & 0xff);
  399.     out.write((v >>> 8) & 0xff);
  400.     written += 2;
  401.     }
  402.  
  403.     /*
  404.      * Writes a 32-bit int to the output stream in little-endian byte order.
  405.      */
  406.     private void writeInt(long v) throws IOException {
  407.     OutputStream out = this.out;
  408.     out.write((int)((v >>>  0) & 0xff));
  409.     out.write((int)((v >>>  8) & 0xff));
  410.     out.write((int)((v >>> 16) & 0xff));
  411.     out.write((int)((v >>> 24) & 0xff));
  412.     written += 4;
  413.     }
  414.  
  415.     /*
  416.      * Writes an ASCII string to the output stream.
  417.      */
  418.     private void writeAscii(String s) throws IOException {
  419.     OutputStream out = this.out;
  420.     byte[] b = new byte[s.length()];
  421.     s.getBytes(0, b.length, b, 0);
  422.     writeBytes(b, 0, b.length);
  423.     }
  424.  
  425.     /*
  426.      * Writes an array of bytes to the output stream.
  427.      */
  428.     private void writeBytes(byte[] b, int off, int len) throws IOException {
  429.     super.out.write(b, off, len);
  430.     written += len;
  431.     }
  432. }
  433.